<html><head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1"></head><body><h1>A crash course in SASL and DIGEST-MD5 for XMPP</h1>

<p><em>
Robert Norris &lt;<a href="mailto:rob@cataclysm.cx">rob@cataclysm.cx</a>&gt;<br>
2003-06-02
</em></p>


<h2>Introduction</h2>

<p>
XMPP requires the use of the <a href="http://web.archive.org/web/20050224191820/http://www.ietf.org/rfc/rfc2222.txt">SASL</a>
<a href="http://web.archive.org/web/20050224191820/http://www.ietf.org/rfc/rfc2831.txt">DIGEST-MD5</a> mechanism in
order to authenticate clients. The RFC itself is difficult to follow in
places, however, the actual functionality the clients are required to
implement in order to successfully authenticate to a DIGEST-MD5 aware
server are minimal.
</p>

<p>
It is the goal of this document to show the basics of SASL and
DIGEST-MD5. Hopefully this will be enough to get SASL newcomers up and
running.
</p>

<p>
Note that this document is not intended to be any kind of authorative
documentation on <a href="http://web.archive.org/web/20050224191820/http://www.jabber.org/ietf/">XMPP</a>, SASL
or DIGEST-MD5. If you're in doubt, read the relevant specs. This
document assumes that you'll never want to use the channel integrity
and encryption features that DIGEST-MD5 provides.
</p>


<h2>Utility code</h2>

<p>
Clients need a few utility functions available:
</p>

<ul>
<li>MD5 hashing function</li>
<li>Base64 encoder</li>
<li>Base64 decoder</li>
</ul>


<h2>Startup</h2>

<p>
After the XML stream has been set up, the server will send a list of
stream features that it supports. In amongst this will be a list of SASL
mechanisms that are supported:
</p>

<pre>&lt;stream:features&gt;
  &lt;mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'&gt;
    &lt;mechanism&gt;DIGEST-MD5&lt;/mechanism&gt;
    &lt;mechanism&gt;PLAIN&lt;/mechanism&gt;
    &lt;mechanism&gt;KERBEROS_V4&lt;/mechanism&gt;
  &lt;/mechanisms&gt;
&lt;/stream:features&gt;
</pre>

<p>
If DIGEST-MD5 is not here, then the rest of this document will be fairly
useless. However, its reasonable to assume that the majority of servers
out there will have DIGEST-MD5 available, since XMPP requires servers to
implement it, and it has no known security holes.
</p>

<p>
To initiate the authentication exchange, the client sends a SASL
authentication request, selecting DIGEST-MD5 as the desired mechanism:

</p><pre>    &lt;auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/&gt;
</pre>


<h2>Step one - challenge from server</h2>

<p>
The server will respond by sending a challenge, something like this:
</p>

<pre>&lt;challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'&gt;
    cmVhbG09ImNhdGFjbHlzbS5jeCIsbm9uY2U9Ik9BNk1HOXRFUUdtMmhoIi
    xxb3A9ImF1dGgiLGNoYXJzZXQ9dXRmLTgsYWxnb3JpdGhtPW1kNS1zZXNz
&lt;/challenge&gt;
</pre>

<p>
The contents of this challenge is encoded using Base64, and might look
like this when decoded:
</p>

<pre>realm="cataclysm.cx",nonce="OA6MG9tEQGm2hh",qop="auth",charset=utf-8,algorithm=md5-sess
</pre>

<p>
The challenge can then be parsed out into a number of comma-seperated
key-value pairs (though not all require values, as indicated below).
Brief descriptions are as follows:
</p>

<ul>

<li>realm (zero or more occurences)<p>
If present (one or more), a list of authentication realms that
the client may attempt to authenticate into. If not present, the
client should ask the user to specify a realm.
</p></li>

<li>nonce (one occurence)<p>
An opaque string that is generated by the server. The client
must send this back to the server as part of its response, which
helps to prevent replay attacks. If the server doesn't send
a nonce, the client should abort the exchange.
</p></li>

<li>qop (zero or one occurence)<p>
A list of "quality of protection" values supported by the
server. One of the returned values should be "auth", to indicate
the server supports authentication. Other values ("auth-int",
"auth-conf") may be returned, but are outside the scope of this
document. If the value "auth" is not present, the client should
abort the exchange.
</p></li>

<li>charset (zero or one occurences)<p>
If present, specifies that the server supports UTF-8 encoding
for the username and password. If not present, the username and
password must be encoded with ISO 8859-1. This is only needed
for backward compatibility with HTTP Digest, and its unlikely
that a XMPP server will ever not send it. If there's more than
one, the client should abort the exchange.
</p></li>

<li>algorithm (one occurence)<p>
Required for backward compatibility with HTTP Digest (which can
use other algorithms besides MD5). It can be ignored, but if the
server doesn't send it, or sends it more than once, the client
should abort the exchange.
</p></li>

</ul>


<h2>Step two - response from client</h2>

<p>
The client is required to respond with the appropriate credentials
(Base64 encoded, of course). An example response might be:
</p>

<pre>&lt;response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'&gt;
    dXNlcm5hbWU9InJvYiIscmVhbG09ImNhdGFjbHlzbS5jeCIsbm9uY2U9Ik
    9BNk1HOXRFUUdtMmhoIixjbm9uY2U9Ik9BNk1IWGg2VnFUclJrIixuYz0w
    MDAwMDAwMSxxb3A9YXV0aCxkaWdlc3QtdXJpPSJ4bXBwL2NhdGFjbHlzbS
    5jeCIscmVzcG9uc2U9ZDM4OGRhZDkwZDRiYmQ3NjBhMTUyMzIxZjIxNDNh
    ZjcsY2hhcnNldD11dGYtOCxhdXRoemlkPSJyb2JAY2F0YWNseXNtLmN4L2
    15UmVzb3VyY2Ui
&lt;/response&gt;
</pre>

<p>
The decoded form of this is:
</p>

<pre>username="rob",realm="cataclysm.cx",nonce="OA6MG9tEQGm2hh",cnonce="OA6MHXh6VqTrRk",nc=00000001,qop=auth,digest-uri="xmpp/cataclysm.cx",response=d388dad90d4bbd760a152321f2143af7,charset=utf-8,authzid="rob@cataclysm.cx/myResource"</pre>

<p>The meaning of the values described here are as follows:</p>

<ul>

<li>username (one occurence)<p>
The user's name in the specified realm, encoded according to the
value of the "charset" directive sent by the server. Must be
present once, or the authentication fails.
</p></li>

<li>realm (zero or one occurence)<p>
The authentication realm that this user's account is in. This is
required if the server specified realms in the first challenge,
and should be set to one of those realms. If this is missing,
it will be set to the empty string.
</p></li>

<li>nonce (one occurence)<p>
The string specified by the server in the nonce option during
the first challenge. If missing or specified more than once,
authentication fails.
</p></li>

<li>cnonce (one occurence)<p>
An opaque string that is generated by the client. The server
will send this back to the client as part of future challenges,
which helps to prevent replay attacks. If missing or specified
more than once, authentication fails.
</p></li>

<li>nc (one occurence)<p>
This is the hexadecimal count of the number of responses
(including this one) that the client has sent with the nonce
value in this request. This is used to help the server detect
replays during subsequent authentication, and is not used here.
Set it to "00000001".
</p></li>

<li>serv-type (one occurence)<p>
Indicates the type of service. This should be set to "xmpp".
</p></li>

<li>host (one occurence)<p>
The DNS hostname or IP address for the service requested (though
the DNS hostname is preferred). For XMPP, this should be set to
the hostname of the remote server.
</p></li>

<li>digest-uri (one occurence)<p>
The full name of the service that the client is trying to
connect to, formed from the serv-type and host options. For
example, the XMPP service on "jabber.org" would have a
digest-uri value of "xmpp/jabber.org".
</p></li>

<li>response (one occurence)<p>
A string of 32 hex digits (with the alphabetic characters
lower-cased) that proves the user knows the password (see
below for details of how to compute this). If
missing or specified more than once, authentication fails.
</p></li>

<li>charset (one occurence)<p>
If present, specifies that the client has used UTF-8 encoding
for the username and password. If not present, the username and
password must be encoded in ISO 8859-1.
</p></li>

<li>authzid (one occurence)<p>
The "authorization ID", encoded in UTF-8. This should be the
full JID (including resource) of the session that the client
wishes to start once authentication is complete.
</p></li>

</ul>

<h3>Computing the response value</h3>

<p>
This is where the magic happens. The value of the response directive is
computed as follows:
</p>

<ol>
<li>Create a string of the form "username:realm:password". Call this string X.</li>
<li>Compute the 16 octet MD5 hash of X. Call the result Y.</li>
<li>Create a string of the form "Y:nonce:cnonce:authzid". Call this string A1.</li>
<li>Create a string of the form "AUTHENTICATE:digest-uri". Call this string A2.</li>
<li>Compute the 32 hex digit MD5 hash of A1. Call the result HA1.</li>
<li>Compute the 32 hex digit MD5 hash of A2. Call the result HA2.</li>
<li>Create a string of the form "HA1:nonce:nc:cnonce:qop:HA2". Call this string KD.</li>
<li>Compute the 32 hex digit MD5 hash of KD. Call the result Z.</li>
</ol>

<p>
The resultant string Z should be sent to the server as the value of the
"response" directive.
</p>


<h2>Step three - challenge from server</h2>

<p>
The server will now authenticate the client based on the presented
credentials. If the authentication failed, the server will return:
</p>

<pre>&lt;response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/&gt;
</pre>

<p>
If it was successful, the server will send a final challenge:
</p>

<pre>&lt;challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'&gt;
    cnNwYXV0aD1lYTQwZjYwMzM1YzQyN2I1NTI3Yjg0ZGJhYmNkZmZmZA==
&lt;/challenge&gt;
</pre>

<p>
This challenge, when decoded, will contain a single directive "rspauth".
This directive is not useful for the purposes of this document, and may
be safely ignored.
</p>


<h2>Step four - response from client</h2>

<p>
The client should respond with an empty response:
</p>

<pre>&lt;response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/&gt;
</pre>


<h2>Step five - result from server</h2>

<p>
At this point, the server should inform the client of successful
authentication, like so:
</p>

<pre>&lt;success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/&gt;
</pre>


<h2>Starting a session</h2>

<p>
The connection is now authenticated. The client must now request an IM
session by sending the following:
</p>

<pre>&lt;iq type='set' id='sess_1'&gt;
  &lt;session xmlns='urn:ietf:params:xml:ns:xmpp-session'/&gt;
&lt;/iq&gt;
</pre>

<p>
Assuming that your username/realm has allowed to start a session as the
authzid that was specified during the SASL handshake, the session start
request will succedd:
</p>

<pre>&lt;iq type='result id='sess_1'/&gt;
</pre>

<p>
The session then continues as normal - get roster, send presence, etc.
</p>
<p></p>

<script language="Javascript">
<!--

// FILE ARCHIVED ON 20050224191820 AND RETRIEVED FROM THE
// INTERNET ARCHIVE ON 20090707154327.
// JAVASCRIPT APPENDED BY WAYBACK MACHINE, COPYRIGHT INTERNET ARCHIVE.
// ALL OTHER CONTENT MAY ALSO BE PROTECTED BY COPYRIGHT (17 U.S.C.
// SECTION 108(a)(3)).

   var sWayBackCGI = "http://web.archive.org/web/20050224191820/";

   function xResolveUrl(url) {
      var image = new Image();
      image.src = url;
      return image.src;
   }
   function xLateUrl(aCollection, sProp) {
      var i = 0;
      for(i = 0; i < aCollection.length; i++) {
         var url = aCollection[i][sProp];         if (typeof(url) == "string") { 
          if (url.indexOf("mailto:") == -1 &&
             url.indexOf("javascript:") == -1
             && url.length > 0) {
            if(url.indexOf("http") != 0) {
                url = xResolveUrl(url);
            }
            url = url.replace('.wstub.archive.org','');
            aCollection[i][sProp] = sWayBackCGI + url;
         }
         }
      }
   }

   xLateUrl(document.getElementsByTagName("IMG"),"src");
   xLateUrl(document.getElementsByTagName("A"),"href");
   xLateUrl(document.getElementsByTagName("AREA"),"href");
   xLateUrl(document.getElementsByTagName("OBJECT"),"codebase");
   xLateUrl(document.getElementsByTagName("OBJECT"),"data");
   xLateUrl(document.getElementsByTagName("APPLET"),"codebase");
   xLateUrl(document.getElementsByTagName("APPLET"),"archive");
   xLateUrl(document.getElementsByTagName("EMBED"),"src");
   xLateUrl(document.getElementsByTagName("BODY"),"background");
   xLateUrl(document.getElementsByTagName("TD"),"background");
   xLateUrl(document.getElementsByTagName("INPUT"),"src");
   var forms = document.getElementsByTagName("FORM");
   if (forms) {
       var j = 0;
       for (j = 0; j < forms.length; j++) {
              f = forms[j];
              if (typeof(f.action)  == "string") {
                 if(typeof(f.method)  == "string") {
                     if(typeof(f.method) != "post") {
                        f.action = sWayBackCGI + f.action;
                     }
                  }
              }
        }
    }


//-->
</script>

</body></html>